﻿using Microsoft.Xna.Framework;

using xEngine.Maths;
using xEngine.DataTypes;
using xEngine.Rendering;

namespace xEngine.Actors.Terrain
{
    public class TerrainQuadTreeNode : QuadTreeNode<TerrainQuadTreeNode, TerrainQuadTree>
    {
        #region Member Variables
        
        public readonly int PositionIndex;
        public readonly int Size;

        private bool _isSplit;

        private QuadTreeNodeVertex[] _vertices;      

        #endregion

        #region Properties

        public Bounds2D Bounds { get; private set; }
        public BoundingBox BoundingBox { get; private set; }

        #endregion

        #region Constructors

        public TerrainQuadTreeNode(int position, int nodeSize, TerrainQuadTreeNode parent, TerrainQuadTree parentTree, int positionIndex)
        {
            Position = position;
            ParentNode = parent;
            ParentTree = parentTree;

            _vertices = new QuadTreeNodeVertex[9];

            PositionIndex = positionIndex;
            Size = nodeSize;

            AddVertices();

            UpdateBounds();

            if (Size >= 4)
                AddChildren();

            if (Depth == 1)
            {
                AddNeighbors();

                _vertices[VertexPosition.TopLeft].Enabled = true;
                _vertices[VertexPosition.TopRight].Enabled = true;
                _vertices[VertexPosition.Center].Enabled = true;
                _vertices[VertexPosition.BottomLeft].Enabled = true;
                _vertices[VertexPosition.BottomRight].Enabled = true; 
            }
        }

        #endregion

        #region Functions

        private void AddVertices()
        {
            switch (Position)
            {
                case NodePosition.TopLeft:
                    _vertices[VertexPosition.TopLeft] = ParentNode._vertices[VertexPosition.TopLeft];
                    _vertices[VertexPosition.TopRight] = ParentNode._vertices[VertexPosition.Top];
                    _vertices[VertexPosition.BottomLeft] = ParentNode._vertices[VertexPosition.Left];
                    _vertices[VertexPosition.BottomRight] = ParentNode._vertices[VertexPosition.Center];
                    break;

                case NodePosition.TopRight:
                    _vertices[VertexPosition.TopLeft] = ParentNode._vertices[VertexPosition.Top];
                    _vertices[VertexPosition.TopRight] = ParentNode._vertices[VertexPosition.TopRight];
                    _vertices[VertexPosition.BottomLeft] = ParentNode._vertices[VertexPosition.Center];
                    _vertices[VertexPosition.BottomRight] = ParentNode._vertices[VertexPosition.Right];
                    break;

                case NodePosition.BottomLeft:
                    _vertices[VertexPosition.TopLeft] = ParentNode._vertices[VertexPosition.Left];
                    _vertices[VertexPosition.TopRight] = ParentNode._vertices[VertexPosition.Center];
                    _vertices[VertexPosition.BottomLeft] = ParentNode._vertices[VertexPosition.BottomLeft];
                    _vertices[VertexPosition.BottomRight] = ParentNode._vertices[VertexPosition.Bottom];
                    break;

                case NodePosition.BottomRight:
                    _vertices[VertexPosition.TopLeft] = ParentNode._vertices[VertexPosition.Center];
                    _vertices[VertexPosition.TopRight] = ParentNode._vertices[VertexPosition.Right];
                    _vertices[VertexPosition.BottomLeft] = ParentNode._vertices[VertexPosition.Bottom];
                    _vertices[VertexPosition.BottomRight] = ParentNode._vertices[VertexPosition.BottomRight];
                    break;

                default:
                    _vertices[VertexPosition.TopLeft] = new QuadTreeNodeVertex { Enabled = true, Index = 0 };
                    _vertices[VertexPosition.TopRight] = new QuadTreeNodeVertex { Enabled = true, Index = _vertices[VertexPosition.TopLeft].Index + Size };
                    _vertices[VertexPosition.BottomLeft] = new QuadTreeNodeVertex { Enabled = true, Index = ParentTree.Terrain.HeightMap.Width * (ParentTree.Terrain.HeightMap.Width - 1) };
                    _vertices[VertexPosition.BottomRight] = new QuadTreeNodeVertex { Enabled = true, Index = _vertices[VertexPosition.BottomLeft].Index + Size };
                    break;
            }

            int halfSize = Size / 2;

            _vertices[VertexPosition.Top] = new QuadTreeNodeVertex { Enabled = false, Index = _vertices[VertexPosition.TopLeft].Index + (halfSize) };
            _vertices[VertexPosition.Left] = new QuadTreeNodeVertex { Enabled = false, Index = _vertices[VertexPosition.TopLeft].Index + ParentTree.Terrain.HeightMap.Width * (halfSize) };
            _vertices[VertexPosition.Center] = new QuadTreeNodeVertex { Enabled = false, Index = _vertices[VertexPosition.Left].Index + (halfSize) };
            _vertices[VertexPosition.Right] = new QuadTreeNodeVertex { Enabled = false, Index = _vertices[VertexPosition.Left].Index + Size };
            _vertices[VertexPosition.Bottom] = new QuadTreeNodeVertex { Enabled = false, Index = _vertices[VertexPosition.BottomLeft].Index + (halfSize) };
        }
        private void AddNeighbors()
        {
            switch (Position)
            {
                case NodePosition.TopLeft:
                    Neighbors[NeighborPosition.Right] = ParentNode.Children[NodePosition.TopRight];
                    Neighbors[NeighborPosition.Bottom] = ParentNode.Children[NodePosition.BottomLeft];

                    if (ParentNode.Neighbors[NeighborPosition.Top] != null)
                        Neighbors[NeighborPosition.Top] = ParentNode.Neighbors[NeighborPosition.Top].Children[NodePosition.BottomLeft];

                    if (ParentNode.Neighbors[NeighborPosition.Left] != null)
                        Neighbors[NeighborPosition.Left] = ParentNode.Neighbors[NeighborPosition.Left].Children[NodePosition.TopRight];
                    break;

                case NodePosition.TopRight:
                    Neighbors[NeighborPosition.Left] = ParentNode.Children[NodePosition.TopLeft];
                    Neighbors[NeighborPosition.Bottom] = ParentNode.Children[NodePosition.BottomRight];

                    if (ParentNode.Neighbors[NeighborPosition.Top] != null)
                        Neighbors[NeighborPosition.Top] = ParentNode.Neighbors[NeighborPosition.Top].Children[NodePosition.BottomRight];

                    if (ParentNode.Neighbors[NeighborPosition.Right] != null)
                        Neighbors[NeighborPosition.Right] = ParentNode.Neighbors[NeighborPosition.Right].Children[NodePosition.TopLeft];
                    break;

                case NodePosition.BottomLeft:
                    Neighbors[NeighborPosition.Right] = ParentNode.Children[NodePosition.BottomRight];
                    Neighbors[NeighborPosition.Top] = ParentNode.Children[NodePosition.TopLeft];

                    if (ParentNode.Neighbors[NeighborPosition.Bottom] != null)
                        Neighbors[NeighborPosition.Bottom] = ParentNode.Neighbors[NeighborPosition.Bottom].Children[NodePosition.TopLeft];

                    if (ParentNode.Neighbors[NeighborPosition.Left] != null)
                        Neighbors[NeighborPosition.Left] = ParentNode.Neighbors[NeighborPosition.Left].Children[NodePosition.BottomRight];
                    break;

                case NodePosition.BottomRight:
                    Neighbors[NeighborPosition.Left] = ParentNode.Children[NodePosition.BottomLeft];
                    Neighbors[NeighborPosition.Top] = ParentNode.Children[NodePosition.TopRight];

                    if (ParentNode.Neighbors[NeighborPosition.Bottom] != null)
                        Neighbors[NeighborPosition.Bottom] = ParentNode.Neighbors[NeighborPosition.Bottom].Children[NodePosition.TopRight];

                    if (ParentNode.Neighbors[NeighborPosition.Right] != null)
                        Neighbors[NeighborPosition.Right] = ParentNode.Neighbors[NeighborPosition.Right].Children[NodePosition.BottomLeft];
                    break;

                default:
                    break;
            }

            if (HasChildren)
                for (int i = 0; i < Children.Length; i++)
                    Children[i].AddNeighbors();
        }
        private void AddChildren()
        {
            int halfSize = Size / 2;

            for (int i = 0; i < Children.Length; i++)
                Children[i] = new TerrainQuadTreeNode(i, halfSize, this, ParentTree, _vertices[(i/2) * 2 + i % 2].Index);
        }
        private bool Split()
        {
            if (!(_isSplit = HasChildren))
            {
                _vertices[VertexPosition.Top].Enabled = true;
                _vertices[VertexPosition.Left].Enabled = true;
                _vertices[VertexPosition.Bottom].Enabled = true;
                _vertices[VertexPosition.Right].Enabled = true;
            }

            // Split Neighbors
            for (int i = 0; i < Neighbors.Length; i++)
                if (Neighbors[i] != null)
                    Neighbors[i].SplitUpwards(Depth - 1);

            if (Neighbors[NeighborPosition.Top] != null)
                Neighbors[NeighborPosition.Top]._vertices[VertexPosition.Bottom].Enabled = true;
            if (Neighbors[NeighborPosition.Bottom] != null)
                Neighbors[NeighborPosition.Bottom]._vertices[VertexPosition.Top].Enabled = true;
            if (Neighbors[NeighborPosition.Right] != null)
                Neighbors[NeighborPosition.Right]._vertices[VertexPosition.Left].Enabled = true;
            if (Neighbors[NeighborPosition.Left] != null)
                Neighbors[NeighborPosition.Left]._vertices[VertexPosition.Right].Enabled = true;

            return _isSplit;
        }
        private void SplitUpwards(int fromDepth)
        {
            // Start splitting from specified depth - ignore deeper nodes
            if(Depth <= fromDepth)
                Split();

            if (ParentNode != null && !ParentNode._isSplit)
                ParentNode.SplitUpwards(fromDepth);
        }
        private Triangle GetTriangle(int a, int b, int c)
        {
            return new Triangle(ParentTree.Vertices[_vertices[a].Index].Position,
                                ParentTree.Vertices[_vertices[b].Index].Position,
                                ParentTree.Vertices[_vertices[c].Index].Position);
        }

        public void GetEnabledVertices()
        {
            // If node is split and has children, use their vertices instead
            if (_isSplit && HasChildren)
            {
                for (int i = 0; i < Children.Length; i++)
                    Children[i].GetEnabledVertices();
                return;
            }

            // ...otherwise, get enabled vertices from a current node

            //Top Triangle(s)
            ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
            ParentTree.AddIndex(_vertices[VertexPosition.TopLeft].Index);

            if (_vertices[VertexPosition.Top].Enabled)
            {
                ParentTree.AddIndex(_vertices[VertexPosition.Top].Index);

                ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
                ParentTree.AddIndex(_vertices[VertexPosition.Top].Index);
            }
            ParentTree.AddIndex(_vertices[VertexPosition.TopRight].Index);

            //Right Triangle(s)
            ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
            ParentTree.AddIndex(_vertices[VertexPosition.TopRight].Index);

            if (_vertices[VertexPosition.Right].Enabled)
            {
                ParentTree.AddIndex(_vertices[VertexPosition.Right].Index);

                ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
                ParentTree.AddIndex(_vertices[VertexPosition.Right].Index);
            }
            ParentTree.AddIndex(_vertices[VertexPosition.BottomRight].Index);

            //Bottom Triangle(s)
            ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
            ParentTree.AddIndex(_vertices[VertexPosition.BottomRight].Index);

            if (_vertices[VertexPosition.Bottom].Enabled)
            {
                ParentTree.AddIndex(_vertices[VertexPosition.Bottom].Index);

                ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
                ParentTree.AddIndex(_vertices[VertexPosition.Bottom].Index);
            }
            ParentTree.AddIndex(_vertices[VertexPosition.BottomLeft].Index);

            //Left Triangle(s)
            ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
            ParentTree.AddIndex(_vertices[VertexPosition.BottomLeft].Index);

            if (_vertices[VertexPosition.Left].Enabled)
            {
                ParentTree.AddIndex(_vertices[VertexPosition.Left].Index);

                ParentTree.AddIndex(_vertices[VertexPosition.Center].Index);
                ParentTree.AddIndex(_vertices[VertexPosition.Left].Index);
            }
            ParentTree.AddIndex(_vertices[VertexPosition.TopLeft].Index);
        }
        public void ResetNode()
        {
            _isSplit = false;

            _vertices[VertexPosition.Top].Enabled = false;
            _vertices[VertexPosition.Bottom].Enabled = false;
            _vertices[VertexPosition.Left].Enabled = false;
            _vertices[VertexPosition.Right].Enabled = false;

            if(HasChildren)
                for (int i = 0; i < Children.Length; i++)
                    Children[i].ResetNode();
        }
        public void SplitDownwards(int toDepth)
        {
            // Keep splitting downwards untill toDepth value has been reached or node cannot be split further
            if (Depth < toDepth && Split())
                for (int i = 0; i < Children.Length; i++)
                    Children[i].SplitDownwards(toDepth);
        }
        public void CalculateDepth(BoundingSphere near, BoundingSphere far)
        {
            if (BoundingBox.Contains(far) == ContainmentType.Disjoint)
                return;

            if (HasChildren)
                for (int i = 0; i < Children.Length; i++)
                    Children[i].CalculateDepth(near, far);

            // If no more children then apply depth level to current node and upwards, based on distance from point provided
            else
            {
                if (BoundingBox.Contains(near) != ContainmentType.Disjoint)
                    SplitUpwards(ParentTree.Terrain.MaxQuality);
                else
                {
                    float distance = Vector3.Distance(XMath.ClosestBoxPointToPoint(BoundingBox, near.Center), near.Center) - ParentTree.Terrain.NearDistance;
                    SplitUpwards((int)((1 - distance / (ParentTree.Terrain.FarDistance - ParentTree.Terrain.NearDistance)) * ParentTree.Terrain.MaxQuality));
                }
            }
        }
        public void CalculateDepth(Vector3 point)
        {
            //Debugging.LogWriter.WriteLine = _bounds + " | p = " + point;

            // If beyond FarDistance, don't bother
            if (!XMath.CircleRectangleIntersection(new Circle(point, ParentTree.Terrain.FarDistance), Bounds) && !Bounds.ContainsPoint(point))
                return;

            // If beyond view frustum, ignore ?
            // Would have to force a refresh every time camera rotates - too CPU expensive ?
            // But perhaps could be optimized

            // If has children, check them for intersections
            if (HasChildren)
                for (int i = 0; i < Children.Length; i++)
                    Children[i].CalculateDepth(point);
            
            // If no more children then apply depth level to current node and upwards, based on distance from point provided
            else
            {
                // Within NearDistance - apply full detail
                if (XMath.CircleRectangleIntersection(new Circle(point, ParentTree.Terrain.NearDistance), Bounds))
                    SplitUpwards(ParentTree.Terrain.MaxQuality);

                // Within FarDistance - calculate and apply gradual fade
                else if (XMath.CircleRectangleIntersection(new Circle(point, ParentTree.Terrain.FarDistance), Bounds))
                {
                    float distance = Vector2.Distance(XMath.ClosestRectanglePointToPoint(Bounds, point), new Vector2(point.X, point.Z)) - ParentTree.Terrain.NearDistance;
                    float deltaDist = ParentTree.Terrain.FarDistance - ParentTree.Terrain.NearDistance;

                    SplitUpwards((int)((1 - distance / deltaDist) * ParentTree.Terrain.MaxQuality));
                }

                // Shouldn't really happen, since everything beyond FarDistance is supposed to be rejected at the beginning
                else { }
            }
        }
        public TerrainQuadTreeNode FindNodeContainingPoint(Vector2 p)
        {
            TerrainQuadTreeNode node = null;

            // If point is located somewhere inside this node check deeper if current node has children or return itself
            if (Bounds.ContainsPoint(p))
                if (HasChildren)
                {
                    for (int i = 0; i < Children.Length; i++)
                        // If node is found no point in checking other nodes
                        if ((node = Children[i].FindNodeContainingPoint(p)) != null)
                            break;
                }
                else
                    node = this;

            // Return found node or null if node not found
            return node;
        }
        public Triangle FindTriangleContainingPoint(Vector2 p)
        {
            Triangle temp;

            temp = GetTriangle(VertexPosition.Center, VertexPosition.TopLeft, VertexPosition.Top);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.Top, VertexPosition.TopRight);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.TopRight, VertexPosition.Right);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.Right, VertexPosition.BottomRight);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.BottomRight, VertexPosition.Bottom);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.Bottom, VertexPosition.BottomLeft);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.BottomLeft, VertexPosition.Left);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            temp = GetTriangle(VertexPosition.Center, VertexPosition.Left, VertexPosition.TopLeft);
            if (temp.ContainsPoint2D(p))
                return temp;
            
            // Should NOT happen!
            return new Triangle();
        }

        public float[] UpdateBounds()
        {
            float max = -0xFFFFFFFF;
            float min = 0xFFFFFFFF;

            if (HasChildren)
                foreach (TerrainQuadTreeNode node in Children)
                {
                    float[] tmp = node.UpdateBounds();
                    if (tmp[0] < min)
                        min = tmp[0]; 
                    if (tmp[1] > max)
                        max = tmp[1];
                }

            foreach(QuadTreeNodeVertex v in _vertices)
            {
                float y = ParentTree.Vertices[v.Index].Position.Y;
                if (y < min)
                    min = y;
                if (y > max)
                    max = y;
            }

            Vector3[] corners = new Vector3[8];

            corners[0] = ParentTree.Vertices[_vertices[VertexPosition.TopLeft].Index].Position;
            corners[1] = ParentTree.Vertices[_vertices[VertexPosition.TopRight].Index].Position;
            corners[2] = ParentTree.Vertices[_vertices[VertexPosition.BottomRight].Index].Position;
            corners[3] = ParentTree.Vertices[_vertices[VertexPosition.BottomLeft].Index].Position;
            corners[4] = ParentTree.Vertices[_vertices[VertexPosition.TopLeft].Index].Position;
            corners[5] = ParentTree.Vertices[_vertices[VertexPosition.TopRight].Index].Position;
            corners[6] = ParentTree.Vertices[_vertices[VertexPosition.BottomRight].Index].Position;
            corners[7] = ParentTree.Vertices[_vertices[VertexPosition.BottomLeft].Index].Position;

            corners[0].Y = corners[1].Y = corners[2].Y = corners[3].Y = min;
            corners[4].Y = corners[5].Y = corners[6].Y = corners[7].Y = max;

            corners[0] = Vector3.Transform(corners[0], ParentTree.Terrain.Transformation.Matrix);
            corners[1] = Vector3.Transform(corners[1], ParentTree.Terrain.Transformation.Matrix);
            corners[2] = Vector3.Transform(corners[2], ParentTree.Terrain.Transformation.Matrix);
            corners[3] = Vector3.Transform(corners[3], ParentTree.Terrain.Transformation.Matrix);
            corners[4] = Vector3.Transform(corners[4], ParentTree.Terrain.Transformation.Matrix);
            corners[5] = Vector3.Transform(corners[5], ParentTree.Terrain.Transformation.Matrix);
            corners[6] = Vector3.Transform(corners[6], ParentTree.Terrain.Transformation.Matrix);
            corners[7] = Vector3.Transform(corners[7], ParentTree.Terrain.Transformation.Matrix);

            Bounds = new Bounds2D(corners[0], corners[2]);
            BoundingBox = BoundingBox.CreateFromPoints(corners);

            return new float[] { min, max };
        }

        public IntersectionResult Intersects(Actor actor)
        {
            IntersectionResult result = actor.BoundingShape.Intersects(BoundingBox);

            if (result.Intersects && HasChildren)
            {
                foreach (TerrainQuadTreeNode node in Children)
                    if ((result = node.Intersects(actor)).Intersects)
                        return result;

                return false;
            }

            return result;
        }
        public IntersectionResult Intersects(Ray ray)
        {
            float? dist = BoundingBox.Intersects(ray);
            IntersectionResult result = new IntersectionResult(dist.HasValue, dist);

            if (result.Intersects && HasChildren)
            {
                foreach (TerrainQuadTreeNode node in Children)
                    if ((result = node.Intersects(ray)).Intersects)
                        return result;

                return false;
            }

            return result;
        }

        #endregion

        static class VertexPosition
        {
            public const int TopLeft = 0;
            public const int Top = 1;
            public const int TopRight = 2;
            public const int Left = 3;
            public const int Center = 4;
            public const int Right = 5;
            public const int BottomLeft = 6;
            public const int Bottom = 7;
            public const int BottomRight = 8;
        }
        private struct QuadTreeNodeVertex
        {
            public int Index;
            public bool Enabled;
        }
    }
}